<!DOCTYPE html>
<html>
    <head>

        <link rel="canonical" href="http://www.jointjs.com/" />
        <meta name="description" content="Create interactive diagrams in JavaScript easily. JointJS plugins for ERD, Org chart, FSA, UML, PN, DEVS, LDM diagrams are ready to use." />
        <meta name="keywords" content="JointJS, JavaScript, diagrams, diagramming library, UML, charts" />

        <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">

        <link rel="stylesheet" href="css/tutorial.css" />
        <link rel="stylesheet" href="../node_modules/prismjs/themes/prism.css">


        <!-- Dependencies: -->
        <script src="../node_modules/jquery/dist/jquery.js"></script>
        <script src="../node_modules/lodash/lodash.js"></script>
        <script src="../node_modules/backbone/backbone.js"></script>


        <link rel="stylesheet" type="text/css" href="../build/joint.min.css" />
        <script type="text/javascript" src="../build/joint.min.js"></script>

        <title>JointJS - JavaScript diagramming library - Getting started.</title>

    </head>
    <body class="language-javascript tutorial-page">

        <script>
            SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function (toElement) {
                return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
            };
        </script>

        <div id="multiple-links-between-elements" class="tutorial">

            <h2>Multiple links between elements</h2>

            <p>We have been asked how to accommodate two or more links connecting to the same two elements.
                By default, multiple links that connect the same two elements simply overlap one another, which makes
                interaction cumbersome.
                This tutorial introduces a solution - a function that automatically creates and adjusts link vertices to
                offset the overlapping links away from each other.
                We will be using the built-in <a href="/docs/geometry" target="_blank">JointJS geometry library</a>.</p>

            <p>In the following demo, try moving an element around to see how the links adjust.
                Then try to reconnect a link (by dragging its source/target arrowhead onto the pink element) and see how
                the other links regroup.</p>

            <div id="paper-multiple-links"></div>

            <p>JointJS source code: <a href="js/multiple-links.js" target="_blank">multiple-links.js</a></p>

            <p>The core of the solution lies in the <code>adjustVertices</code> function presented below.
                It accepts a <code>graph</code> and a <code>cell</code> (link or element).
                For added convenience, the function accepts cell views as well as models.</p>

            <ul>
                <li>If <code>cell</code> is a link, it will find all links with the same source and target and then set
                vertices on them; we will be calling those related links 'siblings'.</li>
                <li>If <code>cell</code> is an element, we execute our function for each distinct (different source and
                target) link connected to the element.</li>
            </ul>

            <pre><code>function adjustVertices(graph, cell) {

    // if `cell` is a view, find its model
    cell = cell.model || cell;

    if (cell instanceof joint.dia.Element) {
        // `cell` is an element

        _.chain(graph.getConnectedLinks(cell))
            .groupBy(function(link) {

                // the key of the group is the model id of the link's source or target
                // cell id is omitted
                return _.omit([link.source().id, link.target().id], cell.id)[0];
            })
            .each(function(group, key) {

                // if the member of the group has both source and target model
                // then adjust vertices
                if (key !== 'undefined') adjustVertices(graph, _.first(group));
            })
            .value();

        return;
    }

    // `cell` is a link
    // get its source and target model IDs
    var sourceId = cell.get('source').id || cell.previous('source').id;
    var targetId = cell.get('target').id || cell.previous('target').id;

    // if one of the ends is not a model
    // (if the link is pinned to paper at a point)
    // the link is interpreted as having no siblings
    if (!sourceId || !targetId) return;

    // identify link siblings
    var siblings = _.filter(graph.getLinks(), function(sibling) {

        var siblingSourceId = sibling.source().id;
        var siblingTargetId = sibling.target().id;

        // if source and target are the same
        // or if source and target are reversed
        return ((siblingSourceId === sourceId) && (siblingTargetId === targetId))
            || ((siblingSourceId === targetId) && (siblingTargetId === sourceId));
    });

    var numSiblings = siblings.length;
    switch (numSiblings) {

        case 0: {
            // the link has no siblings
            break;

        } case 1: {
            // there is only one link
            // no vertices needed
            cell.unset('vertices');
            break;

        } default: {
            // there are multiple siblings
            // we need to create vertices

            // find the middle point of the link
            var sourceCenter = graph.getCell(sourceId).getBBox().center();
            var targetCenter = graph.getCell(targetId).getBBox().center();
            var midPoint = g.Line(sourceCenter, targetCenter).midpoint();

            // find the angle of the link
            var theta = sourceCenter.theta(targetCenter);

            // constant
            // the maximum distance between two sibling links
            var GAP = 20;

            _.each(siblings, function(sibling, index) {

                // we want offset values to be calculated as 0, 20, 20, 40, 40, 60, 60 ...
                var offset = GAP * Math.ceil(index / 2);

                // place the vertices at points which are `offset` pixels perpendicularly away
                // from the first link
                //
                // as index goes up, alternate left and right
                //
                //  ^  odd indices
                //  |
                //  |---->  index 0 sibling - centerline (between source and target centers)
                //  |
                //  v  even indices
                var sign = ((index % 2) ? 1 : -1);

                // to assure symmetry, if there is an even number of siblings
                // shift all vertices leftward perpendicularly away from the centerline
                if ((numSiblings % 2) === 0) {
                    offset -= ((GAP / 2) * sign);
                }

                // make reverse links count the same as non-reverse
                var reverse = ((theta < 180) ? 1 : -1);

                // we found the vertex
                var angle = g.toRad(theta + (sign * reverse * 90));
                var vertex = g.Point.fromPolar(offset, angle, midPoint);

                // replace vertices array with `vertex`
                sibling.vertices([vertex]);
            });
        }
    }
}</code></pre>

            <p>JointJS source code: <a href="js/multiple-links.js" target="_blank">multiple-links.js</a> (lines
                3-115)</p>

            <p>We then attach the necessary event listeners (function <code>bindInteractionEvents</code>).
                The vertices are recalculated anytime the user translates an element - as well as anytime a link is
                added/removed or has its source or target changed.</p>

            <pre><code>// bind `graph` to the `adjustVertices` function
var adjustGraphVertices = _.partial(adjustVertices, graph);

// adjust vertices when a cell is removed or its source/target was changed
graph.on('add remove change:source change:target', adjustGraphVertices);

// adjust vertices when the user stops interacting with an element
paper.on('cell:pointerup', adjustGraphVertices);</code></pre>

            <p>JointJS source code: <a href="js/multiple-links.js" target="_blank">multiple-links.js</a> (lines
                117-127)</p>

        </div><!--end tutorial-->

        <script src="../node_modules/prismjs/prism.js"></script>

        <script src="js/multiple-links.js"></script>
    </body>
</html>
